use rand::Rng;

use std::collections::HashMap;
use std::io::{Error, ErrorKind};
use prost::Message as ProtoMessage;
use std::str::from_utf8;
use sha2::{Sha256, Digest};
use bytes::{Buf, BufMut};

extern crate alloc;
use std::mem::MaybeUninit;
use alloc::vec::Vec;
use alloc::alloc::{alloc, dealloc, Layout};
use std::slice;

mod pb {
    include!("../../wasm-lib/src/pb.rs");
}
include!("../../wasm-lib/src/core.rs");

static mut SECRET:i32 = 0;

#[no_mangle]
pub extern fn allocate(size: u32) -> u64 {
    // let list:Vec<u8> = Vec::with_capacity(size as usize);
    // let value = Box::new(list);
    // let ptr = Box::into_raw(value);
    
    // Allocate the amount of bytes needed.
    // let vec: Vec<MaybeUninit<u8>> = Vec::with_capacity(size as usize);

    // into_raw leaks the memory to the caller.
    // let ptr = Box::into_raw(vec.into_boxed_slice()) as *mut u8;

    let layout = Layout::from_size_align(size as usize, 1).unwrap();
    unsafe {
        let ptr = alloc(layout);
        if ptr.is_null(){
            panic!("out of memory!");
        }
        println!("> allocate: {}, {}", ptr as u64, size);
        return ptr as u64;
    }
}

#[no_mangle]
pub extern fn deallocate(ptr: u64, size: u32) {
    let layout = Layout::from_size_align(size as usize, 1).unwrap();
    unsafe {
        // let _ = Box::from_raw(ptr as *mut u8);
        dealloc(ptr as *mut u8, layout);
        println!("> deallocate: {}", ptr);
    }
}

#[no_mangle]
pub extern fn start() {
    unsafe {
        SECRET = generate();
    }
}

fn ptr_to_string(ptr: u32, len: u32) -> String {
    unsafe {
        let list = slice::from_raw_parts_mut(ptr as *mut u8, len as usize);
        let utf8 = std::str::from_utf8_unchecked_mut(list);
        return String::from(utf8);
    }
}

fn string_to_ptr(s: &String) -> (u32, u32) {
    return (s.as_ptr() as u32, s.len() as u32);
}

#[no_mangle]
pub extern fn process(input: u64) -> u64 {
    let offset:u32 = (input >> 32) as u32;
    let size:u32 = input as u32;
    let input_str = ptr_to_string(offset, size);
    println!("> input: {}, {}, {}", offset, size, input_str);
    unsafe {
        let ret_str = guess(SECRET, input_str);
        let (ret_offset, ret_size) = string_to_ptr(&ret_str);
        println!("> output: {}, {}, {}", ret_offset, ret_size, ret_str);
        return (ret_offset as u64) << 32 | (ret_size as u64);
    }
}

#[no_mangle]
pub extern fn stop() {
}

#[test]
fn service_test() {
    let input = String::from("410a110a066e756d62657212070a053620000000");
    let input_bytes = input.as_bytes();

    let ll = input_bytes.len();
    println!("input: {}, {}", ll, input);
    let output = guess(20, input);
    println!("output: {}, {}", output.len(), output);
}

fn generate() -> i32 {
    let secret = rand::thread_rng().gen_range(1..=100);
    println!("> generate: {}", secret);
    return secret;
}

fn _guess(secret: i32, num: i32) -> i32 {
    if num < secret {
        return -1;
    }else if num == secret {
        return 0;
    }else{
        return 1;
    }
}

fn guess(secret: i32, input: String) -> String {
    if input.len() == 0 {
        return "".to_string()
    }
    println!("> input: {}, {}",input.len(), input);
    let input_map = decode_map_from_string(&input);
    match input_map {
        Some(m) => {
            let mut s:String = "".to_owned();
            if let Ok(Some(num)) = get_int32_from_map(&m, "number") {
                let result = _guess(secret, num);

                let mut mm = HashMap::<String, pb::Data>::new();
                mm.insert("result".to_string(), pb::Data{
                    bytes: encode_i32(result),
                });
                println!("> result: {}", result);
                let nm = pb::DataMap{
                    map: mm,
                };
                s = encode_map_to_string(&nm);
            }
            return s;
        }
        _ => {
            return "".to_string()
        }
    }
}

#[test]
fn flow_test() {
    let secret = generate();

    let num:i32 = 32;
    let mut mm = HashMap::<String, pb::Data>::new();
    mm.insert("number".to_string(), pb::Data{
        bytes: encode_i32(num),
    });
    let m = pb::DataMap{
        map: mm,
    };
    let input:String = encode_map_to_string(&m);
    let result:String = guess(secret, input);
    println!("result: {}", result);
}

use std::fs;
use std::io;
use std::io::Write as _;
use std::io::prelude::*;

#[no_mangle]
pub extern fn cat() {
    let path = "./test.txt";
    println!("file: {}", &path);
    if let Ok(mut f) = fs::File::open(&path) {
        let mut content = String::new();
        f.read_to_string(&mut content).unwrap();
        println!("content: {}", &content);
    } else {
        writeln!(io::stderr(), "error opening: {}: No such file or directory", path).unwrap();
    }
}

#[no_mangle]
pub extern fn write() {
    let path = "./abc.txt";
    println!("file: {}", &path);
    if let Ok(mut f) = fs::File::create(&path) {
        writeln!(f, "12 x 34 = {}", 12*34).unwrap();
    } else {
        writeln!(io::stderr(), "error opening: {}: No such file or directory", path).unwrap();
    }
}

#[no_mangle]
pub extern fn clear() {
    let path = "./test.txt";
    println!("clear: {}", &path);
    fs::remove_file(&path).unwrap();
}

#[no_mangle]
pub extern fn create_dir() {
    let path = "./newDir";
    println!("mkdir: {}", &path);
    fs::create_dir(&path).unwrap();
}

#[no_mangle]
pub extern fn remove_dir() {
    let path = "./newDir";
    println!("rmdir: {}", &path);
    fs::remove_dir(&path).unwrap();
}

use std::fmt::Write as _;

#[no_mangle]
pub extern fn init_dir() {
    let path = "./.git";
    println!("init: {}", &path);
    fs::create_dir(&path).unwrap();

    let mut s = String::new();
    write!(&mut s, "{}/branches", path);
    fs::create_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/objects", path);
    fs::create_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/objects/info", path);
    fs::create_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/objects/pack", path);
    fs::create_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/hooks", path);
    fs::create_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/hooks/hello.sample", path);
    println!("file: {}", &s);
    if let Ok(mut f) = fs::File::create(&s) {
        write!(f, "hello, {}", 12*34).unwrap();
    } else {
        writeln!(io::stderr(), "error opening: {}: No such file or directory", s).unwrap();
    }
}

#[no_mangle]
pub extern fn clear_dir() {
    let path = "./.git";
    println!("clear: {}", &path);
    
    let mut s = String::new();
    write!(&mut s, "{}/hooks/hello.sample", path);
    fs::remove_file(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/hooks", path);
    fs::remove_dir(&s).unwrap();
    s.clear();

    let mut s = String::new();
    write!(&mut s, "{}/branches", path);
    fs::remove_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/objects/info", path);
    fs::remove_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/objects/pack", path);
    fs::remove_dir(&s).unwrap();
    s.clear();

    write!(&mut s, "{}/objects", path);
    fs::remove_dir(&s).unwrap();
    s.clear();

    fs::remove_dir(&path).unwrap();
}

extern crate libc;
use libc::ENOTDIR;

#[no_mangle]
pub extern fn list() {
    let dir = "./";
    println!("list: {}", &dir);
    if let Ok(paths) = fs::read_dir(&dir) {
        for ent in paths.into_iter() {
            println!("item: {}", ent.unwrap().path().display());
        }
        println!("end list");
    }
    println!("done");
}

#[test]
fn fs_test() {
    cat();
    list();
}